const PI2 = Math.PI * 2;
function Position(x, y) {
    this.x = x;
    this.y = y;
}

function Vector2(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
}

let maxId = new Date().getTime();
//游戏对象的父对象
function GameObject() {

    //子类通过调用次方法，将属性初始化到子类，解决父类的成员变量被共享问题
    this.init = function () {

        this.name;
        this.ctx;
        this.layer;
        this.loader;
        this.speed = 0;
        this.moving = false;
        this.autoDestory = false;
        this.id = ++maxId;

        this.initialized = false;

        this._centerX = 0;
        this._centerY = 0;

        //最大长度
        this.maxLength = 0;

        //是否工作，非工作状态就不会被渲染
        this.working = true;

        this._xspeed = 0;
        this._yspeed = 0;
        this._lastX = 0;
        this._lastY = 0;

        //旋转中心点
        this.rotateCenterX = 0;
        this.rotateCenterY = 0;

        //基本中心点，只读变量，内部对象定义并计算
        this._baseCenterX = 0;
        this._baseCenterY = 0;

        //禁用碰撞
        this.collisionDisabled = false;

        this.fillStyle = 'balck';
        this.strokeStyle = 'black';

        this.components = [];
        this.collisionEvents = [];

        this._dots = [];
        this._edges = [];

        //碰撞绑定 
        this._collisionBinding = null;
        this.bindCollisionMask = function (collisionObject) {
            if (collisionObject === this) {
                //不能绑定自己
                return;
            }
            this._collisionBinding = collisionObject;
            this._collisionBinding.autoDestory = true;
            //TODO:解决这里不能使用layer问题
            // this.layer.addObject(collisionObject);
        }

        this.onPropertyValueChange = function (name, newValue, oldValue) {

        }

        this._onPropertyValueChange = function (name, newValue, oldValue) {
            if (this._collisionBinding) {
                let bind = this._collisionBinding;
                let offsetVal = newValue - oldValue;
                switch (name) {
                    case 'x':
                    case 'y':
                    case 'direction':
                    case 'rotation':
                        bind[name] += offsetVal;
                        break;
                    default:
                }
            }
            this.onPropertyValueChange(name, newValue, oldValue);
        }
        var me = this;

        let inner = {
            _width: 0,
            _height: 0,
            //运动方向
            _direction: 0,
            _x: 0,
            _y: 0,
            //旋转角度
            _rotation: 0
        };
        Object.defineProperty(this, 'position', {
            get: () => {
                return { x: inner._x, y: inner._y };
            },
            set: val => {
                if (inner._x != val.x) {
                    let oldVal = inner._x;
                    inner._x = val.x;
                    me._onPropertyValueChange('x', val.x, oldVal);
                }
                if (inner._y != val.y) {
                    let oldVal = inner._y;
                    inner._y = val.y;
                    me._onPropertyValueChange('y', val.y, oldVal);
                }
            }
        });

        for (let propName of ['width', 'height', 'direction', 'rotation', 'x', 'y']) {
            let innerName = '_' + propName;
            Object.defineProperty(this, propName, {
                get: () => inner[innerName],
                set: val => {
                    let innerValue = inner[innerName];
                    if (innerValue != val) {
                        let oldVal = innerValue;
                        inner[innerName] = val;
                        me._onPropertyValueChange(propName, val, oldVal);
                    }
                }
            });
        }
    }

    /**
     * 增加碰撞检测事件
     * @param {*} other 被检测对象或者类型，如果是类型的话会，每次检测 所有存在的类型实例
     * @param {*} handler 如果发生碰撞后的处理函数
     */
    this.addCollisionEvent = function (other, handler, elseHandler) {
        if (!other || !handler) {
            return;
        }
        let targets = [];
        if (other instanceof GameObject) {
            //对象
            targets.push(other);
        } else if (other instanceof Array) {
            for (let o of other) {
                if (o instanceof GameObject) {
                    targets.push(o);
                }
            }
        } else if (other.prototype.constructor.name === GameObject.name) {
            //GameObject的子对象
            targets = this.layer.lookupByType(other);
        }
        this.collisionEvents.push({
            target: targets,
            handler: handler,
            elseHandler: elseHandler
        });
    }

    this.addComponent = function (component) {
        this.components.push(component);
    }

    this.ready = function () { }
    this._ready = function () {
        this.ready();
    }
    this.__ready = function () {
        this._ready();
        for (const cmp of this.components) {
            cmp.ready.call(this);
        }
    };

    this.draw = function () { }
    this._draw = function () {
        this.draw();
    }
    //顶层对象的方法，系统调用这里
    this.__draw = function () {
        //内部对象的方法
        this._draw();
        for (const cmp of this.components) {
            if (cmp.working) {
                cmp.draw.call(this);
            }
        }
    };

    this.update = function (delta) { }
    this._update = function (delta) {
        this.update(delta);
    }
    function processCollision() {
        for (const event of this.collisionEvents) {
            let targets = event.target;
            for (const target of targets) {
                if (target === this) {
                    continue;
                }
                if (CollisionUtil.metting(this, target)) {
                    event.handler.call(target, {
                        source: this,
                        target: target
                    });
                } else {
                    if (event.elseHandler) {
                        event.elseHandler.call(target, {
                            source: this,
                            target: target
                        });
                    }
                }
            }
        }
    }

    this.__update = function (delta) {
        //先调用固定逻辑
        if (this.moving && this.speed > 0) {
            let xScale = Math.cos(this.direction);
            let yScale = Math.sin(this.direction);
            this.x += xScale * delta * this.speed;
            this.y += yScale * delta * this.speed;
        }
        processCollision.call(this);
        //再调用内部对象逻辑
        this._update(delta);
        for (const cmp of this.components) {
            if (cmp.working) {
                cmp.update.call(this, delta);
            }
        }
        //计算x,y向的速度
        this._xspeed = (this.x - this._lastX) / delta;
        this._yspeed = (this.y - this._lastY) / delta;
        this._lastY = this.y;
        this._lastX = this.x;
    };

    this.tryDestory = function () {
        if (!this.autoDestory) {
            return;
        }
        if (this.x < 0 || this.x > this.ctx.canvas.width
            || this.y < 0 || this.y > this.ctx.canvas.height) {
            this.destory();
        }
    }
    this.destory = function () {
        this.layer.removeObject(this);
        if (this._collisionBinding) {
            this.layer.removeObject(this._collisionBinding);
            this._collisionBinding = null;
        }
    }
}

/**
 * 组件
 */
let GameComponent = {
    /**
     * 上下左右移动
     */
    WSADMove: function (speed) {
        speed = speed || 0;
        this.init();
        let keymap = {
            up: 'w',
            down: 's',
            left: 'a',
            right: 'd',
        };
        this.setKeymap = function (upKey, downKey, leftKey, rightKey) {
            keymap.up = upKey;
            keymap.down = downKey;
            keymap.left = leftKey;
            keymap.right = rightKey;
        }
        this.update = function (delta) {
            if (Input.keybord_down_check(keymap.left)) {
                this.x -= speed * delta;
            }
            if (Input.keybord_down_check(keymap.right)) {
                this.x += speed * delta;
            }
            if (Input.keybord_down_check(keymap.up)) {
                this.y -= speed * delta;
            }
            if (Input.keybord_down_check(keymap.down)) {
                this.y += speed * delta;
            }
        }
    },
    LookAt: function (target) {
        this.init();
        this.update = function (delta) {
            let targetX = target.getX();
            let targetY = target.getY();

            let thisX = this._baseCenterX + this.rotateCenterX;
            let thisY = this._baseCenterY + this.rotateCenterY;

            // let minD = 5;
            // if (Math.abs(thisX - targetX) <= minD || Math.abs(thisY - targetY) <= minD) {
            //     //如果距离“足够近”了，就不再旋转角度，避免完全相交的时候不停的抖动
            //     return;
            // }

            this.rotation = MathUtil.calcLineRotation(thisX, thisY, targetX, targetY);
            this.direction = this.rotation;
        }
    },
    /**
     * 跟随玩家旋转
     */
    LookAtMouse: function () {
        this.init();
        let lookAt = new GameComponent.LookAt({
            getX: function () {
                return Input.mouseX;
            },
            getY: function () {
                return Input.mouseY;
            }
        });
        this.update = function (delta) {
            lookAt.update.call(this, delta);
        }
    },
    LookAtObject: function (target) {
        this.init();

        let lookAt = new GameComponent.LookAt({
            getX: function () {
                return target._centerX;
            },
            getY: function () {
                return target._centerY;
            }
        });
        this.update = function (delta) {
            lookAt.update.call(this, delta);
        }
    },
    MouseDrag: function () {
        this.init();
        let dragging = false;
        let isInArea = false;
        let clickOther = false;
        let mouseX = -1;
        let mouseY = -1;
        this.update = function (delta) {
            let clicking = Input.mouse_down_check('left');
            if (!clicking) {
                dragging = false;
                //如果不再点击，需要取消其它区域点击状态，重新判断是否点击的当前区域
                clickOther = false;
                mouseX = -1;
                mouseY = -1;
                return;
            }
            if (clickOther) {
                return;
            }
            if (!dragging) {
                isInArea = CollisionUtil.isInArea(this, Input.mouseX, Input.mouseY);
                if (isInArea) {
                    //点击的是当前区域
                    dragging = true;
                } else {
                    //点击的是其它区域
                    clickOther = true;
                }
            }
            if (dragging) {
                if (mouseX != -1 && mouseY !== -1) {
                    this.x = this.x + (Input.mouseX - mouseX);
                    this.y = this.y + (Input.mouseY - mouseY);
                }
                mouseX = Input.mouseX;
                mouseY = Input.mouseY;
            }
        }
    },
    /**
     * 
     * 发生子弹组件
     * 假设子弹和武器初始都是朝右侧方向
     * 子弹左侧没有多余的空白图
     * 
     * @param {*} bullet  子弹
     * @param {*} rotation 角度
     * @param {*} speed 速度
     * @param {*} cooldown 冷却时间，间隔多少帧发射一次
     */
    Shot: function (creator, tigger, speed, cooldown, offset) {
        this.init();
        let count = cooldown;
        offset = (offset || {});
        offset.x = offset.x || 0;
        offset.y = offset.y || 0;
        this.update = function (delta) {
            count--;
            if (tigger() && count <= 0) {
                count = cooldown;
                let bullet = creator();

                let bulletCenterX = this.x + this.width + (bullet._centerX - bullet.x) + offset.x;
                let bulletCenterY = this._baseCenterY + offset.y;

                let absRotateCenterX = this._baseCenterX + this.rotateCenterX;
                let absRotateCenterY = this._baseCenterY + this.rotateCenterY;
                let rad = MathUtil.rotateLine(absRotateCenterX, absRotateCenterY, bulletCenterX, bulletCenterY, this.rotation);
                let s = MathUtil.calcLineLength(absRotateCenterX, absRotateCenterY, bulletCenterX, bulletCenterY);


                bulletCenterX = absRotateCenterX + s * Math.cos(rad);
                bulletCenterY = absRotateCenterY + s * Math.sin(rad);

                bullet.x = bulletCenterX - (bullet._centerX - bullet.x);
                bullet.y = bulletCenterY - (bullet._centerY - bullet.y);

                bullet.direction = this.direction;
                bullet.rotation = this.rotation;
                bullet.speed = speed;
                bullet.moving = true;
                this.layer.addObject(bullet);
            }
        }
    },
    Surround: function (target, speed) {
        this.init();
        let rotation = 0;
        let radius;
        this.ready = function () {
            rotation = MathUtil.calcLineRotation(this._centerX, this._centerY, target._centerX, target._centerY);
            radius = MathUtil.calcLineLength(this._centerX, this._centerY, target._centerX, target._centerY);
        }
        this.update = function (delta) {
            //周长
            let girth = PI2 * radius;
            //增加的角度
            let angleInc = speed * delta / girth * PI2;
            //最新的角度,这里注意新的角度超过360°的情况需要处理
            rotation = (rotation + angleInc) % (2 * Math.PI);

            this.x = target._centerX + radius * Math.cos(rotation) - (this._centerX - this.x);
            this.y = target._centerY + radius * Math.sin(rotation) - (this._centerY - this.y);
        }
    },
    Tip: function (text, offsetX, offsetY) {
        this.init();
        this.text = text || '';

        let x = 0;
        let y = 0;
        this.ready = function () {
            offsetX = offsetX || this.width / 2;
            offsetY = offsetY || this.height / 2;
        }

        this.update = function () {
            x = this._centerX;
            y = this._centerY;
        };
        this.draw = function () {
            this.ctx.save();
            this.ctx.font = "15px Verdana";
            this.ctx.fillText(text + '(' + this._centerX.toFixed() + ',' + this._centerY.toFixed() + ')', x + this.width / 2, y - this.height / 2);
            this.ctx.restore();
        }
    },
    AxisDottedLine: function () {
        this.init();
        let lineToX = {};
        let lineToY = {};
        this.update = function (delta) {
            lineToX.x1 = this._centerX;
            lineToX.y1 = 0;
            lineToX.x2 = this._centerX;
            lineToX.y2 = this._centerY;

            lineToY.x1 = 0;
            lineToY.y1 = this._centerY;
            lineToY.x2 = this._centerX;
            lineToY.y2 = this._centerY;
        }
        this.draw = function () {
            this.ctx.save();
            this.ctx.beginPath();
            this.ctx.globalAlpha = 0.2;
            this.ctx.setLineDash([5, 5]);
            this.ctx.moveTo(lineToX.x1, lineToX.y1);
            this.ctx.lineTo(lineToX.x2, lineToX.y2);
            this.ctx.moveTo(lineToY.x1, lineToY.y1);
            this.ctx.lineTo(lineToY.x2, lineToY.y2);
            this.ctx.stroke();
            this.ctx.closePath();
            this.ctx.restore();
        }
    },
    Connect: function (target) {
        this.init();
        let arrow = new ArrowObject();

        this.ready = function () {
            arrow._ready();
            arrow.ctx = this.ctx;
            arrow.layer = this.layer;
        }
        this.update = function (delta) {
            arrow.x = this.x + (target.x - this.x) / 2;
            arrow.y = this.y + (target.y - this.y) / 2;
            let r = MathUtil.calcLineRotation(this.x, this.y, target.x, target.y);
            arrow.rotation = r;
            arrow.arrowLen = MathUtil.calcLineLength(this.x, this.y, target.x, target.y);
            arrow._update(delta);
        }
        this.draw = function () {
            arrow._draw();
        }
    },
    MoveToClick: function (speed) {
        this.init();
        let clickDot = { x: null, y: null };
        function stop() {
            moving = false;
            connector.working = false;
            clickDot.x = null;
            clickDot.y = null;
        }
        function start() {
            moving = true;
            clickDot.x = Input.mouseX;
            clickDot.y = Input.mouseY;
            connector.working = true;
        }
        let _speed = speed || 10;
        let moving = false;
        let connector;
        this.ready = function () {
            connector = new GameComponent.Connect(clickDot);
            connector.working = false;
            this.addComponent(connector);
        }
        this.update = function (delta) {
            if (Input.mouse_down_check('left')) {
                start();
            }
            if (!moving) {
                return;
            }
            let step = _speed * delta;
            if (Math.abs(this.x - clickDot.x) <= step && Math.abs(this.y - clickDot.y) <= step) {
                stop();
                return;
            }
            let dx = clickDot.x - this.x;
            let dy = clickDot.y - this.y;
            let d = Math.sqrt(dx * dx + dy * dy);
            this.x += dx / d * step;
            this.y += dy / d * step;
        }
    },
    SpeedTip: function () {
        this.init();
        let _x;
        let _y;
        let speed = 0;
        this.ready = function () {
            _x = this.x;
            _y = this.y;
        }
        let _delta = 0;
        let counter = 0;
        let MAX_COUNT = 5;
        this.update = function (delta) {
            counter++;
            _delta += delta;
            if (counter < MAX_COUNT) {
                return;
            }
            counter = 0;
            let dx = this.x - _x;
            let dy = this.y - _y;
            _x = this.x;
            _y = this.y;
            let d = Math.sqrt(dx * dx + dy * dy);
            speed = d / _delta;
            _delta = 0;
        }
        this.draw = function () {
            this.ctx.save();
            this.ctx.font = "15px Verdana";
            this.ctx.fillText('V:' + speed.toFixed()
                + ',XV:' + this._xspeed.toFixed(1)
                + ',YV:' + this._yspeed.toFixed(1),
                this._centerX + this.width / 2, this._centerY - this.height / 3);
            this.ctx.restore();
        }
    },
    Shake: function (tigger) {
        this.init();
        //0-静止;1-正向震动;2-负向震动
        let state = 0;
        //震动经历时间
        let elapsed = 0;
        const SINGLE_RANG = 10;
        let range = 0;
        let duration = 100 / 1000;
        let shaking = false;
        let me = this;
        this.update = function (delta) {
            if (state > 0) {
                //处理事件间隔
                if (elapsed < duration) {
                    elapsed += delta;
                    return;
                }
                shaking = true;
                elapsed = 0;
                switch (state) {
                    case 1:
                        range = SINGLE_RANG;
                        state = 2;
                        break;
                    case 2:
                        range = -2 * SINGLE_RANG;
                        state = 3;
                        break;
                    case 3:
                        range = SINGLE_RANG;
                        state = 0;
                        me.shakeFinish();
                }
            } else {
                if (typeof tigger === 'function' && tigger()) {
                    state = 1;
                    elapsed = 0;
                }
            }
        };
        this.draw = function () {
            if (shaking) {
                this.ctx.translate(range, range);
                shaking = false;
            }
        }
        this.shake = function () {
            state = 1;
            elapsed = 0;
        }
        this.shakeFinish = function () { }
    },
    MouseThrow: function () {
        this.init();
        let holding = false;
        let throwing = false;
        this.update = function () {
            if (Input.mouse_down_check('left')) {
                if (CollisionUtil.isInArea(this, Input.mouseX, Input.mouseY)) {
                    holding = true;
                }
            } else {
                if (holding) {
                    throwing = true;
                }
                holding = false;
            }
            if (throwing) {
                this.speed = Math.sqrt(this._xspeed * this._xspeed + this._yspeed * this._yspeed) / 10;
                this.direction = MathUtil.calcDotRotation(this._xspeed, this._yspeed);
                this.moving = true;
                console.log('抛开了', this.speed, MathUtil.rad2deg(this.direction));
                throwing = false;
            } else {
                if (holding) {
                    console.log('抓住了');
                    this.x = Input.mouseX;
                    this.y = Input.mouseY;
                }
            }
        }
    },
    Gravity: function (gravity) {
        this.init();
        this.gravity = gravity == undefined ? 9.8 : gravity;
        this.speed = 0;
        let me = this;
        // let friction = 0;
        this.heightInc = 0;
        this.update = function (delta) {
            let speedInc = delta * me.gravity;
            me.heightInc = me.speed * delta + delta * speedInc / 2;
            this.y += me.heightInc;
            me.speed += speedInc;
        }
    },

    DrawingRect: function () {
        this.init();

        let rect = {
            x1: 0,
            y1: 0,
            x2: 0,
            y3: 0
        };

        const STATE_START = 'START';
        const STATE_READY = 'READY';
        const STATE_DRAWING = 'DRAWING';
        const STATE_COMPLETE = 'COMPLETE';

        let state = STATE_START;

        let rectObj = new RectObject();

        this.ready = function () {
            rectObj._ready();
            rectObj.ctx = this.ctx;
            rectObj.layer = this.layer;
        };

        this.update = function (delta) {
            delta *= 100;
            switch (state) {
                case STATE_START:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_READY;
                        console.log('准备绘画');
                    }
                    break;
                case STATE_READY:
                    if (!Input.mouse_down_check('left')) {
                        state = STATE_DRAWING;
                        rect.x1 = Input.mouseX;
                        rect.y1 = Input.mouseY;
                        console.log('绘画中');
                    }
                    break;
                case STATE_DRAWING:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_COMPLETE;
                        console.log('完成绘画');
                    } else {
                        rect.x2 = Input.mouseX;
                        rect.y2 = Input.mouseY;
                    }
                    break;
                case STATE_COMPLETE:
                    break;
            }
        };

        this.draw = function () {
            if (state !== STATE_START) {
                rectObj.width = rect.x2 - rect.x1;
                rectObj.height = rect.y2 - rect.y1;
                rectObj.x = rect.x1;
                rectObj.y = rect.y1;
                rectObj._draw();
            }
        }
    },

    DrawingArc: function () {
        this.init();

        let arc = {
            x1: 0,
            y1: 0,
            radius: 0,
        };

        const STATE_START = 'START';
        const STATE_READY = 'READY';
        const STATE_DRAWING = 'DRAWING';
        const STATE_COMPLETE = 'COMPLETE';

        let state = STATE_START;

        let arcObj = new ArcObject();
        let originDot = new ArcObject(1);

        this.ready = function () {
            arcObj._ready();
            arcObj.ctx = this.ctx;
            arcObj.layer = this.layer;

            originDot._ready();
            originDot.ctx = this.ctx;
            originDot.layer = this.layer;
        };

        this.update = function (delta) {
            delta *= 100;
            switch (state) {
                case STATE_START:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_READY;
                        console.log('准备绘画');
                    }
                    break;
                case STATE_READY:
                    if (!Input.mouse_down_check('left')) {
                        state = STATE_DRAWING;
                        arc.x = Input.mouseX;
                        arc.y = Input.mouseY;
                        originDot.x = Input.mouseX;
                        originDot.y = Input.mouseY;
                        console.log('绘画中', arc);
                    }
                    break;
                case STATE_DRAWING:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_COMPLETE;
                        console.log('完成绘画', arc);
                    } else {
                        arc.radius = MathUtil.calcLineLength(arc.x, arc.y, Input.mouseX, Input.mouseY);
                    }
                    break;
                case STATE_COMPLETE:
                    break;
            }
        };

        this.draw = function () {
            if (state !== STATE_START) {
                arcObj.x = arc.x;
                arcObj.y = arc.y;
                arcObj.radius = arc.radius;
                arcObj._draw();

                originDot._draw();
            }
        }
    },

    DrawingArc2: function () {
        this.init();

        let arc = {
            x: 0,
            y: 0,
            origin: {
                x: 0,
                y: 0,
            },
            radius: 0,
        };

        const STATE_START = 'START';
        const STATE_READY = 'READY';
        const STATE_DRAWING = 'DRAWING';
        const STATE_COMPLETE = 'COMPLETE';

        let state = STATE_START;

        let arcObj = new ArcObject();
        let originDot = new ArcObject(1);

        this.ready = function () {
            arcObj._ready();
            arcObj.ctx = this.ctx;
            arcObj.layer = this.layer;

            originDot._ready();
            originDot.ctx = this.ctx;
            originDot.layer = this.layer;
        };

        function calcOrigin(x1, y1, x2, y2) {
            return {
                x: x1 + (x2 - x1) / 2,
                y: y1 + (y2 - y1) / 2
            };
        }

        this.update = function (delta) {
            delta *= 100;
            switch (state) {
                case STATE_START:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_READY;
                        console.log('准备绘画');
                    }
                    break;
                case STATE_READY:
                    if (!Input.mouse_down_check('left')) {
                        state = STATE_DRAWING;
                        arc.x = Input.mouseX;
                        arc.y = Input.mouseY;
                        console.log('绘画中', arc);
                    }
                    break;
                case STATE_DRAWING:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_COMPLETE;
                        console.log('完成绘画', arc);
                    } else {
                        let origin = calcOrigin(arc.x, arc.y, Input.mouseX, Input.mouseY);
                        arc.origin = origin;
                        arc.radius = MathUtil.calcLineLength(origin.x, origin.y, Input.mouseX, Input.mouseY);
                        originDot.x = origin.x;
                        originDot.y = origin.y;
                    }
                    break;
                case STATE_COMPLETE:
                    break;
            }
        };

        this.draw = function () {
            if (state !== STATE_START) {
                arcObj.x = arc.origin.x;
                arcObj.y = arc.origin.y;
                arcObj.radius = arc.radius;
                arcObj._draw();

                originDot._draw();
            }
        }
    },

    DrawingArrow: function () {
        this.init();

        let arc = {
            x1: 0,
            y1: 0,
            x2: 0,
            y2: 0,
            rotation: 0
        };

        const STATE_START = 'START';
        const STATE_READY = 'READY';
        const STATE_DRAWING = 'DRAWING';
        const STATE_COMPLETE = 'COMPLETE';

        let state = STATE_START;

        let arrow = new ArrowObject();

        this.ready = function () {
            arrow._ready();
            arrow.ctx = this.ctx;
            arrow.layer = this.layer;
        };

        this.update = function (delta) {
            delta *= 100;
            switch (state) {
                case STATE_START:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_READY;
                        console.log('准备绘画');
                    }
                    break;
                case STATE_READY:
                    if (!Input.mouse_down_check('left')) {
                        state = STATE_DRAWING;
                        arc.x1 = Input.mouseX;
                        arc.y1 = Input.mouseY;
                        console.log('进入绘画中', arrow);
                    }
                    break;
                case STATE_DRAWING:
                    if (Input.mouse_down_check('left')) {
                        state = STATE_COMPLETE;
                        console.log('完成绘画', arc);
                    } else if (Input.mouse_down_check('right')) {
                        state = STATE_START;
                        arc = {
                            x1: 0,
                            y1: 0,
                            x2: 0,
                            y2: 0,
                            rotation: 0
                        }
                        arrow.x = 0;
                        arrow.y = 0;
                        arrow.rotation = 0;
                        console.log('取消绘画', arc);
                    } else {
                        arc.x2 = Input.mouseX;
                        arc.y2 = Input.mouseY;

                        arrow.x = arc.x1 + (arc.x2 - arc.x1) / 2;
                        arrow.y = arc.y1 + (arc.y2 - arc.y1) / 2;
                        let r = MathUtil.calcLineRotation(arc.x1, arc.y1, arc.x2, arc.y2);
                        arrow.rotation = r;
                        arrow.arrowLen = MathUtil.calcLineLength(arc.x1, arc.y1, arc.x2, arc.y2);
                    }
                    break;
                case STATE_COMPLETE:
                    break;
            }
        };

        this.draw = function () {
            if (state !== STATE_START) {
                arrow._draw();
            }
        }

        this.isCompleted = function () {
            return state === STATE_COMPLETE;
        }
    }
};

/**
 * 2023-08-16 补充
 * 直接使用 类型.prototype==原型对象 的方式指定原型，会导致创建出来的对象的construct也指向原型
 * 为了能识别出类型 不能使用同一个原型对象
 * 
 */

// 又发现一个js的坑：
// 多个函数指向多个原型对象实例会导致原型对象的变量被共享，所以成员变量都不能放到原型对象里面，而是使用init方法初始化到子对象 
// 但是使用一个原型实例又会导致instanceof判断出来类型都一样，所以下面这样写也不行，还得给每个子对象设置独立的原型对象
// let _GameObject = new GameObject();
GameComponent.WSADMove.prototype = new GameObject();
GameComponent.LookAt.prototype = new GameObject();
GameComponent.LookAtMouse.prototype = new GameObject();
GameComponent.LookAtObject.prototype = new GameObject();
GameComponent.Shot.prototype = new GameObject();
GameComponent.Surround.prototype = new GameObject();
GameComponent.MouseDrag.prototype = new GameObject();
GameComponent.Tip.prototype = new GameObject();
GameComponent.AxisDottedLine.prototype = new GameObject();
GameComponent.Connect.prototype = new GameObject();
GameComponent.MoveToClick.prototype = new GameObject();
GameComponent.SpeedTip.prototype = new GameObject();
GameComponent.Shake.prototype = new GameObject();
GameComponent.MouseThrow.prototype = new GameObject();
GameComponent.Gravity.prototype = new GameObject();
GameComponent.DrawingRect.prototype = new GameObject();
GameComponent.DrawingArc.prototype = new GameObject();
GameComponent.DrawingArc2.prototype = new GameObject();
GameComponent.DrawingArrow.prototype = new GameObject();

function ArcObject(radius) {
    this.init();
    this.radius = radius || 0;
    this.width = this.radius * 2;
    this.height = this.radius * 2;

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
                this._centerX = newValue;
                this._baseCenterX = newValue;
                break;
            case 'y':
                this._centerY = newValue;
                this._baseCenterY = newValue;
                break;
        }
    };

    this._draw = function () {
        this.ctx.beginPath();
        if (this.rotation > 0) {
            this.ctx.save();
            this.ctx.translate(this.x, this.y);
            this.ctx.rotate(this.rotation);
            this.ctx.translate(0 - this.x, 0 - this.y);
            this.ctx.strokeStyle = this.strokeStyle;
            this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
            this.ctx.restore();
        } else {
            this.ctx.strokeStyle = this.strokeStyle;
            this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        }
        this.ctx.stroke();
        this.ctx.closePath();
        this.draw();
    };

    this._update = function (delta) {
        this.update(delta);
    };

    this._ready = function () {
        this.maxLength = this.radius * 2;
        this._centerX = this.x;
        this._centerY = this.y;
        this.ready();
    };
}
ArcObject.prototype = new GameObject();

function RectObject() {
    this.init();
    this.degeCount = 4;

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
            case 'y':
            case 'width':
            case 'height':
            case 'rotation':
                this.calcCenter();
                MathUtil.calcRectDotAndEdges(this);
                break;
        }
    }
    let absRotateCenterX = this._baseCenterX + this.rotateCenterX;
    let absRotateCenterY = this._baseCenterY + this.rotateCenterY;
    this.calcCenter = function () {
        this.maxLength = Math.sqrt(this.width * this.width + this.height * this.height);

        this._baseCenterX = this.x + this.width / 2;
        this._baseCenterY = this.y + this.height / 2;

        absRotateCenterX = this._baseCenterX + this.rotateCenterX;
        absRotateCenterY = this._baseCenterY + this.rotateCenterY;

        let s = Math.sqrt(this.rotateCenterX * this.rotateCenterX + this.rotateCenterY * this.rotateCenterY);
        let rad = this.rotation + MathUtil.calcDotRotation(-this.rotateCenterX, -this.rotateCenterY);
        //计算矩形的真实中心点
        this._centerX = absRotateCenterX + s * Math.cos(rad);
        this._centerY = absRotateCenterY + s * Math.sin(rad);
    }

    /**
     * 计算矩形的点和边
     */
    this.calcDotAndEdges = function () {
        if (this._dots.length == 0) {
            return;
        }
        let line1 = this.height / 2 + this.rotateCenterY; //旋转中心到上边
        let line2 = this.width / 2 - this.rotateCenterX;//旋转中心到右边
        let line3 = this.height / 2 - this.rotateCenterY;//旋转中心到下边
        let line4 = this.width / 2 + this.rotateCenterX;//旋转中心到左边

        let half1 = Math.sqrt(line1 * line1 + line4 * line4); //左上对角线
        let half2 = Math.sqrt(line1 * line1 + line2 * line2); //右上对角线
        let half3 = Math.sqrt(line2 * line2 + line3 * line3); //右下对角线
        let half4 = Math.sqrt(line3 * line3 + line4 * line4); //左下对角线

        let rad1 = (Math.PI + Math.atan(line1 / line4) + this.rotation) % PI2; //左上点
        let rad2 = (PI2 - Math.atan(line1 / line2) + this.rotation) % PI2; //右上点
        let rad3 = (Math.atan(line3 / line2) + this.rotation) % PI2; //右下点
        let rad4 = (Math.PI - Math.atan(line3 / line4) + this.rotation) % PI2; //左下点

        this._dots[0].x = absRotateCenterX + half1 * Math.cos(rad1);
        this._dots[0].y = absRotateCenterY + half1 * Math.sin(rad1);

        this._dots[1].x = absRotateCenterX + half2 * Math.cos(rad2);
        this._dots[1].y = absRotateCenterY + this.rotateCenterY + half2 * Math.sin(rad2);

        this._dots[2].x = absRotateCenterX + half3 * Math.cos(rad3);
        this._dots[2].y = absRotateCenterY + this.rotateCenterY + half3 * Math.sin(rad3);

        this._dots[3].x = absRotateCenterX + half4 * Math.cos(rad4);
        this._dots[3].y = absRotateCenterY + this.rotateCenterY + half4 * Math.sin(rad4);

        for (let i = 1; i < this._dots.length; i++) {
            const dot = this._dots[i];
            let preIndex = i - 1;
            this._edges[preIndex].x1 = this._dots[preIndex].x;
            this._edges[preIndex].y1 = this._dots[preIndex].y;
            this._edges[preIndex].x2 = dot.x;
            this._edges[preIndex].y2 = dot.y;
        }

        let index = this.degeCount - 1;
        this._edges[index].x1 = this._dots[index].x;
        this._edges[index].y1 = this._dots[index].y;
        this._edges[index].x2 = this._dots[0].x;
        this._edges[index].y2 = this._dots[0].y;

        for (const edge of this._edges) {
            if (edge.size == undefined) {
                let s1 = edge.x1 - edge.x2;
                let s2 = edge.y1 - edge.y2;
                edge.size = Math.sqrt(s1 * s1 + s2 * s2);
            }
        }
    };

    this._ready = function () {
        this._dots = [{}, {}, {}, {}];
        this._edges = [{}, {}, {}, {}];
        this.calcCenter();
        MathUtil.calcRectDotAndEdges(this);
        this.ready();
    };

    this._draw = function () {
        if (this.rotation > 0) {
            this.ctx.save();
            let _centerX = absRotateCenterX;
            let _centerY = absRotateCenterY;
            this.ctx.translate(_centerX, _centerY);
            this.ctx.rotate(this.rotation);
            this.ctx.translate(0 - _centerX, 0 - _centerY);
            this.ctx.strokeStyle = this.strokeStyle;
            this.ctx.strokeRect(this.x, this.y, this.width, this.height);
            this.ctx.restore();
        } else {
            this.ctx.strokeStyle = this.strokeStyle;
            this.ctx.strokeRect(this.x, this.y, this.width, this.height);
        }
        this.draw();
    }
}
RectObject.prototype = new GameObject();


function PolygonObject() {
    this.init();
    let _radius = 1;
    this.degeCount = 3;
    let me = this;

    Object.defineProperty(this, 'radius', {
        get: () => _radius,
        set: val => {
            if (_radius != val) {
                _radius = val;
                me.width = _radius * 2;
                me.height = _radius * 2;
                me.maxLength = _radius * 2;
                me.calcDotAndEdges();
            }
        }
    });
    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
            case 'y':
            case 'rotation':
                this._centerX = this.x;
                this._centerY = this.y;
                this._baseCenterX = this.x;
                this._baseCenterY = this.y;
                me.calcDotAndEdges();
                break;
        }
    }


    /**
     * 计算多边形的点和边
     */
    this.calcDotAndEdges = function () {
        if (!this._dots || this._dots.length == 0) {
            return;
        }
        let radInc = Math.PI * 2 / this.degeCount;
        for (let i = 0; i < this.degeCount; i++) {
            //初始角度+旋转增量+自身的选择角度
            let rad = (this.rotation + radInc * i) % PI2;
            this._dots[i].x = this.x + this.radius * Math.cos(rad);
            this._dots[i].y = this.y + this.radius * Math.sin(rad);
        }
        for (let i = 1; i < this._dots.length; i++) {
            const dot = this._dots[i];
            let preIndex = i - 1;
            this._edges[preIndex].x1 = this._dots[preIndex].x;
            this._edges[preIndex].y1 = this._dots[preIndex].y;
            this._edges[preIndex].x2 = dot.x;
            this._edges[preIndex].y2 = dot.y;
        }

        let index = this.degeCount - 1;
        this._edges[index].x1 = this._dots[index].x;
        this._edges[index].y1 = this._dots[index].y;
        this._edges[index].x2 = this._dots[0].x;
        this._edges[index].y2 = this._dots[0].y;

        for (const edge of this._edges) {
            if (edge.size == undefined) {
                let s1 = edge.x1 - edge.x2;
                let s2 = edge.y1 - edge.y2;
                edge.size = Math.sqrt(s1 * s1 + s2 * s2);
            }
        }
    };
    this._ready = function () {
        this._centerX = this.x;
        this._centerY = this.y;
        for (let i = 0; i < this.degeCount; i++) {
            this._dots.push({});
            this._edges.push({});
        }
        this.calcDotAndEdges();
        this.ready();
    };
    this._update = function (delta) {
        this.update(delta);
    }

    this._draw = function () {
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.shadowOffsetX = 2;
        this.ctx.shadowOffsetY = 2;
        this.ctx.shadowBlur = 2;
        this.ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

        this.ctx.shadowBlur = 10;
        this.ctx.strokeStyle = this.strokeStyle;
        this.ctx.moveTo(this._dots[0].x, this._dots[0].y);
        for (let i = 1; i < this._dots.length; i++) {
            let dot = this._dots[i];
            this.ctx.lineTo(dot.x, dot.y);
        }
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        this.draw();
    }
}
PolygonObject.prototype = new GameObject();

function SpriteObject() {
    this.init();
    this.degeCount = 4;

    let playing = true;
    let me = this;

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
            case 'y':
            case 'width':
            case 'height':
            case 'rotation':
                this.calcCenter();
                MathUtil.calcRectDotAndEdges(this);
                break;
        }
    }
    let absRotateCenterX = this._baseCenterX + this.rotateCenterX;
    let absRotateCenterY = this._baseCenterY + this.rotateCenterY;
    this.calcCenter = function () {
        this._baseCenterX = this.x + this.width / 2;
        this._baseCenterY = this.y + this.height / 2;

        absRotateCenterX = this._baseCenterX + this.rotateCenterX;
        absRotateCenterY = this._baseCenterY + this.rotateCenterY;

        let s = Math.sqrt(this.rotateCenterX * this.rotateCenterX + this.rotateCenterY * this.rotateCenterY);
        let rad = this.rotation + MathUtil.calcDotRotation(-this.rotateCenterX, -this.rotateCenterY);
        //计算矩形的真实中心点
        this._centerX = absRotateCenterX + s * Math.cos(rad);
        this._centerY = absRotateCenterY + s * Math.sin(rad);
    }

    let frames = {};
    function _findFrame(frameName) {
        frameName = frameName || defaultFrameName;
        let frame = frames[frameName];
        if (!frame) {
            let frameInterval = 5;
            frame = {
                image: null,
                images: [],
                frameCount: 0,
                frameInterval: frameInterval, //默认5个时间单位切换一帧
                frameIndex: 0,
                frameCountDown: frameInterval,
                reset: function () {
                    this.frameIndex = 0;
                    this.frameCountDown = this.frameInterval;
                    this.image = this.images[0];
                }
            };
            frames[frameName] = frame;
        }
        return frame;
    }
    this.findFrame = function (frameName) {
        let frame = frames[frameName || defaultFrameName];
        return frame;
    }

    const DEFAULT_FRAME_NAME = 'default';
    let defaultFrameName = DEFAULT_FRAME_NAME;
    let currentFrame = '';
    _findFrame(currentFrame);
    this.loadImage = function (imgPath, frameName) {
        let frame = _findFrame(frameName);
        frame.image = ResLoader.loadImage(imgPath);
        frame.images.push(this.image);
        frame.frameCount = 1;
    }
    this.loadImageSet = function (imgPathArr, frameInterval, frameName) {
        if (imgPathArr.length == 0) {
            return;
        }
        let frame = _findFrame(frameName);

        frame.image = ResLoader.loadImage(imgPathArr[0]);
        for (const imgPath of imgPathArr) {
            frame.images.push(ResLoader.loadImage(imgPath));
        }
        frame.frameCount = imgPathArr.length;
        frame.frameInterval = frameInterval || 5;
    }
    this.initSize = function (frame) {
        this.width = frame.image.img.width;
        this.height = frame.image.img.height;
        this.maxLength = Math.sqrt(this.width * this.width + this.height * this.height);
    }
    this.setImage = function (image, frameName) {
        let frame = _findFrame(frameName);

        frame.image = image;
        frame.images.push(this.image);
        frame.frameCount = 1;
        this.initSize(frame);
    }
    this.setImageSet = function (images, frameInterval, frameName) {
        if (images.length == 0) {
            return;
        }
        let frame = _findFrame(frameName);
        frame.image = images[0];
        for (const image of images) {
            frame.images.push(image);
        }
        frame.frameCount = frame.images.length;
        frame.frameInterval = frameInterval || 5;
        this.initSize(frame);
    }
    this.setDefaultFrame = function (frameName) {
        defaultFrameName = frameName;
    }
    this.onAnimateFinish = function () {
        console.log('播放完成');
    };

    function nextFrame() {
        let frame = _findFrame(currentFrame);
        if (frame.frameCountDown <= 0) {
            frame.frameCountDown = frame.frameInterval;
            if (frame.frameIndex >= frame.frameCount - 1) {
                frame.frameIndex = -1;
                me.onAnimateFinish();
            }
            frame.frameIndex++;
            frame.image = frame.images[frame.frameIndex];
        }
        frame.frameCountDown--;
    }
    this.play = function (frameName) {
        playing = true;
        currentFrame = frameName || defaultFrameName;
    };

    this.pause = function () {
        playing = false;
    };

    let flip = {
        x: 1,
        y: 1,
    };
    this.flipX = function (forward) {
        flip.x = Math.sign(forward);
    };
    this.flipY = function (forward) {
        flip.y = Math.sign(forward);
    };

    this.reset = function () {
        let frame = _findFrame(currentFrame);
        frame.reset();
    };

    this._ready = function () {
        let frame = _findFrame(currentFrame);
        this.initSize(frame);
        this._dots = [{}, {}, {}, {}];
        this._edges = [{}, {}, {}, {}];
        MathUtil.calcRectDotAndEdges(this);
        this.ready();
    }

    this._update = function (delta) {
        let frame = _findFrame(currentFrame);
        if (frame.frameCount > 1) {
            if (playing) {
                nextFrame();
            }
        }
        this.update(delta);
    };

    this._draw = function () {
        let frame = _findFrame(currentFrame);
        let image = frame.image;
        if (!image || !image.isReady) {
            return;
        }
        this.width = image.img.width;
        this.height = image.img.height;
        if (this.rotation > 0) {
            this.ctx.save();
            this.ctx.translate(this._centerX, this._centerY);
            this.ctx.rotate(this.rotation);
            this.ctx.translate(0 - this._centerX, 0 - this._centerY);
            this.ctx.scale(flip.x, flip.y);
            this.ctx.drawImage(image.img, this.x, this.y);
            this.ctx.restore();
        } else {
            this.ctx.save();
            this.ctx.translate(this._centerX, this._centerY);
            this.ctx.scale(flip.x, flip.y);
            this.ctx.translate(0 - this._centerX, 0 - this._centerY);
            this.ctx.drawImage(image.img, this.x, this.y);
            this.ctx.restore();
        }
        this.draw();
    }
}
SpriteObject.prototype = new GameObject();

//居中
const FILL_TYPE_CENTER = 'center';
//适应
const FILL_TYPE_FIT = 'fit';
//平铺
const FILL_TYPE_TILE = 'tile';
function BackgroundObject() {
    this.init();
    this.image = null;
    this.fillType = '';

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
            case 'y':
            case 'width':
            case 'height':
                this._centerX = this.x + this.width / 2;
                this._centerY = this.y + this.height / 2;
                this._baseCenterX = this._centerX;
                this._baseCenterY = this._centerY;
                break;
        }
    }

    this.loadImage = function (imgPath) {
        this.image = ResLoader.loadImage(imgPath);
    }
    this.setImage = function (image) {
        this.image = image;
    }
    this._ready = function () {
        this.width = this.image.img.width;
        this.height = this.image.img.height;
        this.maxLength = Math.sqrt(this.width * this.width + this.height * this.height);
        this.ready();
    }

    this._update = function (delta) {
        this.update(delta);
    };

    this._draw = function () {
        if (!this.image || !this.image.isReady) {
            return;
        }
        let canvas = this.ctx.canvas;
        let img = this.image.img;
        switch (this.fillType) {
            case FILL_TYPE_CENTER:
                this.ctx.drawImage(img, canvas.width / 2 - img.width / 2, canvas.height / 2 - img.height / 2);
                break;
            case FILL_TYPE_FIT:
                this.ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                break;
            case FILL_TYPE_TILE:
                for (let i = 0; i * img.width < canvas.width; i++) {
                    for (let j = 0; j * img.height < canvas.height; j++) {
                        this.ctx.drawImage(img, i * img.width, j * img.height);
                    }
                }
                break;
            default:
                this.ctx.drawImage(img, this.x, this.y);
                break;
        }
        this.draw();
    }
}
BackgroundObject.prototype = new GameObject();

/**
 * 血条
 * 是否两个矩形表示
 * 第一个矩形填充颜色，表示剩余血量；第二个矩形不填充，表示失去的血量
 * 初始化默认满血
 * 
 * @param {*} total 总血量，默认1
 * @param {*} color 雪条颜色，默认红色
 */
function Health(total, color) {
    this.init();

    color = color || 'red';
    this.total = total || 1;
    let _value = total;
    this.increase = function (value) {
        _value = Math.max(this.totals, _value + (value || 1));
    }

    this.reduce = function (value) {
        _value = Math.max(0, _value - (value || 1));
    }

    this.setValue = function (value) {
        _value = Math.max(0, value);
    }

    this.isZero = function () {
        return _value <= 0;
    }

    let rect1_width;
    let rect2_width;
    this.ready = function () {
        this.width = this.width == 0 ? 80 : this.width;
        this.height = this.height == 0 ? 10 : this.height;
    }
    this.update = function () {
        //计算矩形宽度
        rect1_width = _value / total * this.width;
        rect2_width = this.width - rect1_width;
    }
    this._draw = function () {
        this.ctx.save();
        this.ctx.fillStyle = color;
        this.ctx.fillRect(this.x, this.y, rect1_width, this.height);
        this.ctx.restore();
        this.ctx.fillRect(this.x + rect1_width, this.y, rect2_width, this.height);

        this.draw();
    }
}
Health.prototype = new GameObject();

function TextObject(text, font) {
    this.init();
    this.text = text || '';
    this.font = font || "20px Verdana";
    this._draw = function () {
        this.ctx.save();
        this.ctx.font = this.font;
        this.ctx.fillText(this.text, this.x, this.y);
        this.ctx.restore();
        this.draw();
    }
}
TextObject.prototype = new GameObject();

/**
 * 组合对象
 * 将多个对象组合在一起，具有如下特点：
 * 1.具有一个主对象，多个从对象
 * 2.主从对象同步移动
 * 3.主从对象都围绕主对象的中心旋转
 * 4.从对象可以有自己的独立运动，同时也可以跟随主对象运行
 * 
 * 运动分两种方式：1.坐标的变化；2.角度变化
 */
function GroupObject() {
    this.init();
    this.name = 'GrouObject';

    let objects = [];

    let me = this;

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        if (name === 'x') {
            let offset = newValue - oldValue;
            for (const object of objects) {
                object.x += offset;
            }
            this._centerX += offset;
        }
        if (name === 'y') {
            let offset = newValue - oldValue;
            for (const object of objects) {
                object.y += offset;
            }
            this._centerY += offset;
        }
        if (name === 'rotation') {
            let offset = newValue - oldValue;
            for (const object of objects) {
                object.rotation += offset;
            }
        }
        if (name === 'direction') {
            let offset = newValue - oldValue;
            for (const object of objects) {
                object.direction += offset;
            }
        }
    }

    this.addObject = function (object) {
        objects.push(object);
        calcSize();
    };

    this.removeObject = function (object) {
        let arr = [];
        for (const obj of objects) {
            if (obj.id !== object.id) {
                arr.push(obj);
            }
        }
        objects = arr;
        calcSize();
    };

    let calcSize = function () {
        let minX = minY = Number.MAX_VALUE;
        let maxX = maxY = Number.MIN_VALUE;
        for (const obj of objects) {
            minX = Math.min(minX, obj.x);
            maxX = Math.max(maxX, obj.x + obj.width);
            minY = Math.min(minY, obj.y);
            maxY = Math.max(maxY, obj.y + obj.height);
        }

        me.width = maxX - minX;
        me.height = maxY - minY;

        me._centerX = minX + me.width / 2;
        me._centerY = minY + me.height / 2;

    };

    this._draw = function () {
        this.draw();
        for (const object of objects) {
            object._draw();
        }
    }
    this._update = function (delta) {
        this.update(delta);
        for (const object of objects) {
            object._update(delta);
        }

    }
    this._ready = function () {
        this.ready();
        for (const object of objects) {
            object._ready();
        }
    }
}
GroupObject.prototype = new GameObject();

function CoordinateObject() {
    this.init();
    this.dot = { x: 0, y: 0 };
    let xStep = 10;
    let yStep = 10;
    let smallScaleHeight = 2;
    let scaleHeight = 5;

    let width;
    let height;
    let context;

    this._ready = function () {
        context = this.ctx;
        width = context.canvas.width;
        height = context.canvas.height;
    };
    this._draw = function () {
        context.save();
        context.globalAlpha = 0.7;
        context.beginPath();
        context.moveTo(0, this.dot.y);
        context.lineTo(width, this.dot.y);
        context.moveTo(this.dot.x, 0);
        context.lineTo(this.dot.x, height);

        context.fillText(0, this.dot.x + 5, this.dot.y - 5);
        for (const sign of [1, -1]) {
            for (let i = 1; ; i++) {
                let inc = sign * i * xStep;
                let x = this.dot.x + inc;
                let y = this.dot.y;
                if (x > width || x < 0) {
                    break;
                }
                context.moveTo(x, y);
                if (i % 10 == 0) {
                    y = this.dot.y + scaleHeight;
                    context.fillText(inc, x - xStep, y + 10);
                } else {
                    y = this.dot.y + smallScaleHeight;
                }
                context.lineTo(x, y);
            }
        }

        for (const sign of [1, -1]) {
            for (let i = 1; ; i++) {
                let inc = sign * i * yStep;
                let y = this.dot.y + inc;
                let x = this.dot.x;
                if (y > height || y < 0) {
                    break;
                }
                context.moveTo(x, y);
                if (i % 10 == 0) {
                    x = this.dot.x + scaleHeight;
                    context.fillText(inc, x + 5, y + 5);
                } else {
                    x = this.dot.x + smallScaleHeight;
                }
                context.lineTo(x, y);
            }
        }
        context.closePath();
        context.stroke();
        context.restore();
        this.draw();
    }
}
CoordinateObject.prototype = new GameObject();

function ArrowObject(arrowLen) {
    this.init();

    let from = { x: 0, y: 0 };
    let to = { y: 0, y: 0 };
    let wing1 = { x: 0, y: 0 };
    let wing2 = { x: 0, y: 0 };

    let _arrowLen = arrowLen || 0;

    let WING_LEN = 10;
    const WING_ROTATION = Math.PI / 6;

    let me = this;

    this.onPropertyValueChange = function (name, newValue, oldValue) {
        switch (name) {
            case 'x':
                this._centerX = newValue;
                me.calDots();
                break;
            case 'y':
                this._centerY = newValue;
                me.calDots();
                break;
            case 'rotation':
                me.calDots();
                break;
        }
    }
    Object.defineProperty(this, 'arrowLen', {
        get: () => _arrowLen,
        set: val => {
            _arrowLen = val;
            me.calDots();
        },
    })

    this.calDots = function (delta) {
        let offsetX = _arrowLen * Math.cos(this.rotation) / 2;
        let offsetY = _arrowLen * Math.sin(this.rotation) / 2;
        from.x = this.x - offsetX;
        from.y = this.y - offsetY;
        to.x = this.x + offsetX;
        to.y = this.y + offsetY;

        let r1 = (Math.PI / 2 - this.rotation) - WING_ROTATION;
        let r2 = (Math.PI / 2 - this.rotation) + WING_ROTATION;

        wing1.x = to.x - WING_LEN * Math.sin(r1);
        wing1.y = to.y - WING_LEN * Math.cos(r1);

        wing2.x = to.x - WING_LEN * Math.sin(r2);
        wing2.y = to.y - WING_LEN * Math.cos(r2);
    }
    this._ready = function () {
        this.calDots();
        this.ready();
    }
    this._draw = function () {
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.globalAlpha = 0.8;
        this.ctx.setLineDash([10, 5]);
        this.ctx.moveTo(from.x, from.y);
        this.ctx.lineTo(to.x, to.y);
        this.ctx.lineTo(wing1.x, wing1.y);
        this.ctx.moveTo(to.x, to.y);
        this.ctx.lineTo(wing2.x, wing2.y);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        this.draw();
    }
}
ArrowObject.prototype = new GameObject();

let ResLoader = new (function () {
    let maxId = 0;
    let readies = {};
    let errors = {};
    this.imageObjs = {};
    this.audioObjs = {};
    this.loadImage = function (imagePath) {
        let imageObj = this.imageObjs[imagePath];
        if (imageObj) {
            console.log('跳过已经加载过的资源：', imagePath);
            return imageObj;
        }
        let image = new Image();
        image.src = imagePath;
        let id = maxId++;
        imageObj = {
            img: image,
            isReady: false,
            id: id,
            src: imagePath
        };

        readies[id] = false;
        image.onload = function () {
            imageObj.isReady = true;
            delete readies[id];
        }
        image.onerror = function () {
            errors[id] = true;
            delete readies[id];
        };
        this.imageObjs[imagePath] = imageObj;
        return imageObj;
    };
    this.loadImageSet = function (imagePathArr) {
        let images = [];
        for (const imagePath of imagePathArr) {
            images.push(this.loadImage(imagePath));
        }
        return images;
    };
    this.loadAudioSet = function (soundPathArr) {
        let audios = [];
        for (const soundPath of soundPathArr) {
            audios.push(this.loadAudio(soundPath));
        }
        return audios;
    }
    this.loadAudio = function (soundPath) {
        let audio = new Audio();
        audio.volume = 0.5;
        audio.src = soundPath;
        audio.preload = 'auto';
        let id = maxId++;

        let audioObj = {
            audio: audio,
            isReady: false,
            id: id,
            src: soundPath
        };
        this.audioObjs[soundPath] = audioObj;

        readies[id] = false;
        audio.addEventListener('canplaythrough', function () {
            audioObj.isReady = true;
            delete readies[id];
        });
        audio.addEventListener('error', function () {
            errors[id] = true;
            delete readies[id];
        })
        return audio;
    };
    this.allReady = function () {
        let count = 0;
        for (const { } in readies) {
            // return false;
            count++;
        }
        return count;
    }
})();

let TiledUtil = {
    loadMap: function (map) {
        let data = {
            tiledata: []
        };

        let tileset = map.tilesets[0].source.tileset;
        data.tilewidth = map.tilewidth;
        data.tileheight = map.tileheight;
        let tiledimageUrl = tileset.image.source;
        data.tilecount = tileset.tilecount;

        let mapLayer = map.layers[0];
        data.mapWidth = map.width;
        data.mapHeight = map.height;

        data.baseX = mapLayer.x;
        data.baseY = mapLayer.y;

        let tiledimage = ResLoader.loadImage(tiledimageUrl);

        let columns = tileset.columns;
        for (let i = 0; i < mapLayer.data.length; i++) {
            let tileIndex = mapLayer.data[i];
            if (tileIndex != 0) {
                let tile = {
                    hidden: false,
                    image: tiledimage.img,
                    sx: ((tileIndex - 1) % columns) * data.tilewidth,
                    sy: parseInt((tileIndex - 1) / columns) * data.tileheight,
                    sWidth: data.tilewidth,
                    sHeight: data.tileheight,
                    dx: data.baseX + i % data.mapWidth * data.tilewidth,
                    dy: data.baseY + parseInt(i / data.mapWidth) * data.tileheight,
                    dWidth: data.tilewidth,
                    dHeight: data.tileheight,
                };
                tile._dx = tile.dx;
                tile._dy = tile.dy;
                data.tiledata.push(tile);
            }
        }
        return data;
    }
};

function InputObject(canvas) {
    this.keydownMap = {}; //按着不放
    this.keypressMap = {}; //按下一次
    this.keyupMap = {}; //放开一次
    this.mousedownMap = {};
    this.mouseupMap = {};
    this.mouseclickMap = {};
    this.mouseX = 0;
    this.mouseY = 0;

    //需要清除的事件
    this.clearInput = function () {
        this.mouseupMap = {};
        this.keyupMap = {};
        this.keypressMap = {};
    }

    this.keybord_pressed_check = function (key) {
        if (!key) {
            return;
        }
        return this.keypressMap[key.toUpperCase()];
    };

    this.keybord_down_check = function (key) {
        if (!key) {
            return;
        }
        return this.keydownMap[key.toUpperCase()];
    };

    this.keybord_up_check = function (key) {
        if (!key) {
            return;
        }
        return this.keyupMap[key.toUpperCase()];
    };

    this.mouse_down_check = function (button) {
        if (!button) {
            return;
        }
        return this.mousedownMap[button.toUpperCase()];
    };

    this.mouse_up_check = function (button) {
        if (!button) {
            return;
        }
        return this.mouseupMap[button.toUpperCase()];
    };

    window.onkeydown = function (event) {
        let key = String.fromCharCode(event.keyCode).toUpperCase();
        Input.keydownMap[key] = true;
    };

    window.onkeypress = function (event) {
        let key = String.fromCharCode(event.keyCode).toUpperCase();
        Input.keypressMap[key] = true;
    };

    window.onkeyup = function (event) {
        let key = String.fromCharCode(event.keyCode).toUpperCase();
        delete Input.keydownMap[key];
        Input.keyupMap[key] = true;
    };

    function mouseConvert(key) {
        let btn = '';
        switch (key) {
            case 0:
                btn = 'LEFT';
                break;
            case 1:
                btn = 'MIDDLE';
                break;
            case 2:
                btn = 'RIGHT';
                break;
        }
        return btn;
    }
    canvas.onmousedown = function (event) {
        let btn = mouseConvert(event.button);
        Input.mousedownMap[btn] = true;
    }
    canvas.onmouseup = function (event) {
        let btn = mouseConvert(event.button);
        delete Input.mousedownMap[btn];
        Input.mouseupMap[btn] = true;
    }
    canvas.onclick = function (event) {
        // Input.mouseclickMap[String.fromCharCode(event.keyCode)] = true;
    }

    canvas.onmousemove = function (event) {
        event = event || window.event;
        if (event.offsetX || event.offsetY) {
            Input.mouseX = event.offsetX;
            Input.mouseY = event.offsetY;
        } else if (event.layerX || event.layerY) {
            //兼容Mozilla浏览器
            Input.mouseX = event.layerX;
            Input.mouseY = event.layerY;
        }
    }
}

let Input;
function Engine(ctx) {
    Input = new InputObject(ctx.canvas);

    let layers = [];
    this.addLayer = function (layer) {
        layers.push(layer);
    }

    this.getLayer = function () {
        return layers;
    };

    // var timer = {
    //     once:true,
    //     enabled:false,
    //     elapsed: 0,
    //     timeout: 10,
    //     handler: function () { }
    // }
    let timers = [];
    this.addTimer = function (handler, timeout, once) {
        if (timeout == undefined) {
            throw new Error('必须设置超时时间');
        }
        let timer = {};
        timer.elapsed = 0;
        timer.timeout = timeout;
        timer.handler = handler;
        timer.once = once == undefined ? false : once;
        timers.push(timer);
        timer.id = timers.length;
        return timer;
    }
    this.removeTimer = function (timer) {
        let _timers = [];
        for (const _timer of timers) {
            if (_timer.id !== timer.id) {
                _timers.push(_timer);
            } else {
                console.log('移除定时器,id:' + timer.id);
            }
        }
        timers = _timers;
    }

    function emitTimer(delta) {
        for (const timer of timers) {
            if (timer.enabled === false) {
                continue;
            }
            timer.elapsed += delta;
            if (timer.elapsed >= timer.timeout) {
                timer.elapsed = 0;
                timer.handler();
                timer.enabled = !timer.once;
            }
        }
    }

    let _interrupt = false;
    this.interrupt = function () {
        _interrupt = true;
        // layers.length = 0;
        timers.length = 0;
        console.log('停止游戏');
    }
    this.destory = function () {
        for (const layer of layers) {
            layer.destory();
        }
        layers.length = 0;
        this.interrupt();
    }

    this.fps = 0;
    let fpsCount = 0;
    let fpsTime = 0;
    let then;
    let mainLoop = function () {
        if (_interrupt) {
            return;
        }

        //计算上一帧执行花费时间
        const now = new Date().getTime();
        let delta = now - (then || now - 1);
        then = now;
        fpsTime += delta;
        if (fpsTime >= 1000) {
            fps = fpsCount;
            fpsCount = 0;
            fpsTime = 0;
            if (fps < 60) {
                console.log('fps:', fps);
            }
        }
        fpsCount++;

        //执行定时器
        emitTimer(delta);

        //更新层
        for (const layer of layers) {
            layer.loop(delta);
        }

        //清除一次性输入
        Input.clearInput();
        window.requestAnimationFrame(mainLoop);
    };

    let _preload = function (loader) {
        for (const layer of layers) {
            layer.preload(loader);
        }
    }
    let _create = function () {
        for (const layer of layers) {
            layer.create();
        }
    }

    let progressBar = null;
    this.loadingCallback = function (loader, count) {
        if (progressBar != null) {
            progressBar.update(count);
            return;
        }
        let textObj = new TextObject();
        textObj.font = "40px Verdana";
        textObj.ctx = ctx;
        textObj.x = ctx.canvas.width / 2 - 160;
        textObj.y = ctx.canvas.height / 2 - 20;

        let prog = new Health(count, 'green');
        prog.ctx = ctx;
        prog.width = 600;
        prog.height = 50;
        prog.x = ctx.canvas.width / 2 - prog.width / 2;
        prog.y = ctx.canvas.height / 2;

        progressBar = {
            update: function (remainder) {
                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
                if (remainder == 0) {
                    progressBar = null;
                    return;
                }
                let loadCount = prog.total - remainder;
                prog.setValue(loadCount);
                prog.update();
                prog._draw();
                textObj.text = '资源加载中...' + (loadCount / prog.total * 100).toFixed(0) + '%';
                textObj._draw();
            }
        }
    }
    let me = this;
    let loading = function () {
        let count = loader.allReady();
        me.loadingCallback(loader, count);
        if (count > 0) {
            console.log('等待资源加载,剩余项：', count);
            window.setTimeout(loading, 100);
        } else {
            _create();
            _interrupt = false;
            window.requestAnimationFrame(mainLoop);
        }
    }
    let loader = ResLoader;
    this.run = function () {
        console.log('启动游戏');
        _preload(loader);
        loading(loader);
    }
}

/**
 * 图层
 *  
 */
function Layer(ctx) {
    this.interval = 0;
    let objects = [];
    this.removeObject = function (obj) {
        let arr = [];
        let count = objects.length;
        for (const object of objects) {
            if (object.id !== obj.id) {
                arr.push(object);
            } else {
                count--;
                if (object.name === 'player')
                    console.log('移除一个对象，剩余对象：', count);
            }
        }
        objects = arr;
    }

    this.addObject = function (object) {
        object.ctx = ctx;
        object.layer = this;
        objects.push(object);
        // console.log('添加对象:', object.name);
    };

    this.printAllObjects = function () {
        console.log(objects);
    };

    this.lookupByType = function (type) {
        let looks = [];
        for (const object of objects) {
            if (object instanceof type) {
                looks.push(object);
            }
        }
        return looks;
    }
    this.clearAll = function () {
        objects.length = 0;
    }
    this.reset = function () {
        objects.length = 0;
        this.preload = function () { }
        this.create = function () { }
    }
    this.destory = function () {
        for (const obj of objects) {
            obj.destory();
        }
        this.reset();
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    }

    let _ready = function () {
        for (const object of objects) {
            if (object.initialized) {
                continue;
            }
            object.__ready()
            object.initialized = true;
        }
    };

    let layer = this;
    let _update = function (delta) {
        layer.update(delta);
        for (const object of objects) {
            if (object.initialized && object.working) {
                object.__update(delta);
            }
        }
    };

    let draw = function () {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        for (const object of objects) {
            if (object.initialized && object.working) {
                object.__draw();
            }
        }
    };

    let _destory = function () {
        for (const object of objects) {
            object.tryDestory();
        }
    };

    let elapsed = null;
    this.loop = function (delta) {
        if (elapsed == null) {
            elapsed = 0;
        } else {
            elapsed += delta;
            if (this.interval > 0 && elapsed < this.interval) {
                return;
            }
        }
        if (this.name == 'bgLayer') {
            console.log('渲染背景层');
        }
        elapsed = 0;
        _destory();
        _ready();
        _update(delta / 100);
        draw();
    }

    //预加载
    this.preload = function (loader) { }
    //创建内容
    this.create = function () { }
    this.update = function (delta) { }
}

let CollisionUtil = {
    rectMettingArc: function (rec, arc) {
        //圆心到矩形中心水平线的距离小于等于半径+矩形高度/2
        //圆心到矩形中心垂直线的距离小于等于半径+矩形宽度/2
        return Math.abs(arc.y - rec.y - rec.height / 2) <= arc.radius + rec.height / 2
            && Math.abs(arc.x - rec.x - rec.width / 2) <= arc.radius + rec.width / 2;
    },
    rectMettingRect: function (rectA, rectB) {
        //x向或者y向的距离 大于对象长度\宽度一半之和
        return Math.abs(rectA.x - rectB.x) <= rectA.width / 2 + rectB.width / 2
            && Math.abs(rectA.y - rectB.y) <= rectA.height / 2 + rectB.height / 2;
    },
    arcMettingArc: function (arcA, arcB) {
        //圆心之间的距离小于两个圆的半径之和
        let distance = MathUtil.calcLineLength(arcA.x, arcA.y, arcB.x, arcB.y);
        return distance <= arcA.radius + arcB.radius;
    },
    /**
     * 简单碰撞检测
     * 
     * 支持没有旋转过的矩形和圆形的检测
     * 
     * @param {*} objectA 
     * @param {*} objectB 
     * @returns 
     */
    simpleMetting: function (objectA, objectB) {
        let isRect = obj => obj instanceof RectObject || obj instanceof SpriteObject;
        let isArc = obj => obj instanceof ArcObject;
        if (isRect(objectA) && isArc(objectB)) {
            return this.rectMettingArc(objectA, objectB);
        }
        if (isArc(objectA) && isRect(objectB)) {
            return this.rectMettingArc(objectB, objectA);
        }
        if (isArc(objectA) && isArc(objectB)) {
            return this.arcMettingArc(objectB, objectA);
        }
        if (isRect(objectA) && isRect(objectB)) {
            return this.rectMettingRect(objectB, objectA);
        }
    },

    /**
     * 高级碰撞检测
     * 
     * 使用分离轴定理碰撞检测算法实现
     * 
     * 支持所有凸面图多边形和圆形的检测
     * 不支持凹面和椭圆图形检测
     * 
     * @param {*} objectA 
     * @param {*} objectB 
     * @returns 
     */
    metting: function (objectA, objectB) {
        objectA = objectA._collisionBinding || objectA;
        objectB = objectB._collisionBinding || objectB;
        //先使用简单但是不准确的方法判断一次，提高效率,大部分情况都没有碰撞，所以这个方法能提交检测效率
        //要保证centerX、centerY位置是在最长线的中心
        let distance = MathUtil.calcLineLength(objectA._centerX, objectA._centerY, objectB._centerX, objectB._centerY);
        if (distance > objectA.maxLength / 2 + objectB.maxLength / 2) {
            return false;
        }
        let isArc = obj => obj instanceof ArcObject;
        if (isArc(objectA) && isArc(objectB)) {
            //都是圆形
            return this.arcMettingArc(objectA, objectB);
        } else if (isArc(objectA) && !isArc(objectB)) {
            //A是圆形,B不是
            return this.SAT.isCoincideArc(objectB, objectA);
        } else if (!isArc(objectA) && isArc(objectB)) {
            //B是圆形,A不是
            return this.SAT.isCoincideArc(objectA, objectB);
        } else {
            //都不是圆形
            //两次判断都是相交，说明是真的相交
            let res = this.SAT.isCoincide(objectA, objectB) && this.SAT.isCoincide(objectB, objectA);
            return res;
        }
    },
    /**
     * 分离轴碰撞检测算法
     * 
     */
    SAT: {
        isOverlay: function (aShadow, bShadow) {
            let aMinX = aShadow[0];
            let aMaxX = aShadow[1];
            let bMinX = bShadow[0];
            let bMaxX = bShadow[1];
            return !(aMaxX < bMinX || aMinX > bMaxX)
        },
        calcShadow: function (object, normal) {
            let minX = null, maxX = null;
            for (let dot of object._dots) {
                let s = dot.x * normal.x + dot.y * normal.y;
                //计算投影最大最小点
                if (minX == null) {
                    minX = s;
                }
                if (maxX == null) {
                    maxX = s;
                }
                minX = Math.min(minX, s);
                maxX = Math.max(maxX, s);
            }
            return [minX, maxX];
        },
        /**
         * 图形A和图形B是否相加
         * 
         * @param {*} objectA 对象A，A对象不能是圆形
         * @param {*} objectB 对象B，B对象可以是圆形
         * @returns  true/false
         */
        isCoincide: function (objectA, objectB) {
            for (let edge of objectA._edges) {
                //法线向量
                let normal = { x: (edge.y2 - edge.y1) / edge.size, y: (edge.x1 - edge.x2) / edge.size };
                //遍历每个点，计算点的X坐标，即得到投影点
                let aShadow = this.calcShadow(objectA, normal);
                let bShadow = this.calcShadow(objectB, normal);
                //比较A,B的投影是否有重合,两条线的中心点之间的距离小于等于两条线的之和的一半，则说明相交
                if (!this.isOverlay(aShadow, bShadow)) {
                    //投影没有相交，说明两个图形一定没有相交，即没有碰撞;跳出循环，返回结果
                    return false;
                }
            }
            return true;
        },
        /**
         * 计算圆形对象旋转之后到X轴的投影
         * @param {*} arcObject 圆形对象
         * @param {*} offsetRad 旋转角度
         * @returns 
         */
        calcArcShadow: function (arcObject, normal) {
            let s = arcObject.x * normal.x + arcObject.y * normal.y;
            return [s - arcObject.radius, s + arcObject.radius];
        },
        isCoincideArc: function (objectA, arcObject) {
            for (let edge of objectA._edges) {
                let normal = { x: (edge.y2 - edge.y1) / edge.size, y: (edge.x1 - edge.x2) / edge.size };
                let aShadow = this.calcShadow(objectA, normal);
                let bShadow = this.calcArcShadow(arcObject, normal);
                if (!this.isOverlay(aShadow, bShadow)) {
                    return false;
                }
            }

            //判断圆上的轴是否为
            let arcCenterX = arcObject.x;
            let arcCenterY = arcObject.y;
            //计算多边形每个点到圆心的最近距离
            let distance = Number.MAX_VALUE;
            let nearDot;
            for (let dot of objectA._dots) {
                let s = MathUtil.calcLineLength(dot.x, dot.y, arcCenterX, arcCenterY);
                if (s <= distance) {
                    nearDot = dot;
                    distance = s;
                }
            }
            let normal = { x: (arcCenterY - nearDot.y1) / distance, y: (nearDot.x1 - arcCenterX) / distance };
            let aShadow = this.calcShadow(objectA, normal);
            let bShadow = this.calcArcShadow(arcObject, normal);
            return this.isOverlay(aShadow, bShadow);
        }
    },
    /**
     * 
     * 指定点(x,y)坐标表示的点是否在图像对象object里面
     * 
     * object为非圆形时的计算方法：
     * 
     * 
     * @param {*} object 
     * @param {*} x 
     * @param {*} y 
     */
    isInArea: function (object, x, y) {
        if (object instanceof ArcObject) {
            let s = MathUtil.calcLineLength(object.x, object.y, x, y);
            return s <= object.radius;
        }

        //计算点和边的投影是否相交
        for (let edge of object._edges) {
            let normal = { x: edge.y2 - edge.y1, y: edge.x1 - edge.x2 };
            let aShadow = this.SAT.calcShadow(object, normal);
            let bShadow = x * normal.x + y * normal.y;
            if (bShadow < aShadow[0] || bShadow > aShadow[1]) {
                return false;
            }
        }
        return true;
    }
};

let DrawUtil = {
    drawText: function (layer, x, y, text) {
        let txtObj = new TextObject(text);
        txtObj.x = x;
        txtObj.y = y;
        layer.addObject(txtObj);
    }
};

let MathUtil = {
    deg2rad: function (deg) {
        return deg * Math.PI / 180;
    },
    rad2deg: function (rad) {
        return rad * 180 / Math.PI;
    },
    /**
     * 计算两个坐标形成的向量的角度
     * 
     * @param {*} startX
     * @param {*} startY 
     * @param {*} endX 
     * @param {*} endY 
     */
    calcLineRotation: function (startX, startY, endX, endY) {
        let x = endX - startX;
        let y = endY - startY;

        // return rad;
        if (x === 0) {
            if (y > 0) {
                rotation = Math.PI / 2;
            } else if (y < 0) {
                rotation = Math.PI * 3 / 2;
            } else {
                rotation = 0;
            }
            return rotation;
        }
        rotation = Math.atan(Math.abs(y / x));
        if (x < 0 && y <= 0) {
            //左上 二象限
            rotation = Math.PI + rotation;
        } else if (x < 0 && y >= 0) {
            //左下 三象限
            rotation = Math.PI - rotation;
        } else if (x > 0 && y <= 0) {
            //右上 一象限
            rotation = PI2 - rotation;
        } else {
            //右下 四象限
        }
        rotation = rotation % PI2;
        return rotation;
    },

    /**
     * 计算点到原点的直线的角度
     * 
     * 
     * @param {*} x 
     * @param {*} y 
     */
    calcDotRotation: function (x, y) {
        return this.calcLineRotation(0, 0, x, y);
    },
    /**
     * 对点进行旋转角度
     * 注意，这里的rotation是旋转之后的角度，而不是此次旋转的角度
     * @param {*} x 
     * @param {*} y 
     * @param {*} rotation 
     * @returns 
     */
    rotateDot: function (x, y, rotation) {
        let r = Math.sqrt(x * x + y * y);
        return {
            x: r * Math.cos(rotation),
            y: r * Math.sin(rotation),
        }
    },
    rotateLine: function (x1, y1, x2, y2, rotation) {
        let rad = MathUtil.calcLineRotation(x1, y1, x2, y2);
        return (rad + rotation) % PI2;
    },
    calcLineLength: function (x1, y1, x2, y2) {
        let d1 = Math.abs(x1 - x2);
        let d2 = Math.abs(y1 - y2);
        return Math.sqrt(d1 * d1 + d2 * d2);
    },

    /**
     * 计算矩形的点和边
     */
    calcRectDotAndEdges: function (rect) {
        if (rect._dots.length == 0) {
            return;
        }
        let absRotateCenterX = rect._baseCenterX + rect.rotateCenterX;
        let absRotateCenterY = rect._baseCenterY + rect.rotateCenterY;

        let line1 = rect.height / 2 + rect.rotateCenterY; //旋转中心到上边
        let line2 = rect.width / 2 - rect.rotateCenterX;//旋转中心到右边
        let line3 = rect.height / 2 - rect.rotateCenterY;//旋转中心到下边
        let line4 = rect.width / 2 + rect.rotateCenterX;//旋转中心到左边

        let half1 = Math.sqrt(line1 * line1 + line4 * line4); //左上对角线
        let half2 = Math.sqrt(line1 * line1 + line2 * line2); //右上对角线
        let half3 = Math.sqrt(line2 * line2 + line3 * line3); //右下对角线
        let half4 = Math.sqrt(line3 * line3 + line4 * line4); //左下对角线

        let rad1 = (Math.PI + Math.atan(line1 / line4) + rect.rotation) % PI2; //左上点
        let rad2 = (PI2 - Math.atan(line1 / line2) + rect.rotation) % PI2; //右上点
        let rad3 = (Math.atan(line3 / line2) + rect.rotation) % PI2; //右下点
        let rad4 = (Math.PI - Math.atan(line3 / line4) + rect.rotation) % PI2; //左下点

        rect._dots[0].x = absRotateCenterX + half1 * Math.cos(rad1);
        rect._dots[0].y = absRotateCenterY + half1 * Math.sin(rad1);

        rect._dots[1].x = absRotateCenterX + half2 * Math.cos(rad2);
        rect._dots[1].y = absRotateCenterY + rect.rotateCenterY + half2 * Math.sin(rad2);

        rect._dots[2].x = absRotateCenterX + half3 * Math.cos(rad3);
        rect._dots[2].y = absRotateCenterY + rect.rotateCenterY + half3 * Math.sin(rad3);

        rect._dots[3].x = absRotateCenterX + half4 * Math.cos(rad4);
        rect._dots[3].y = absRotateCenterY + rect.rotateCenterY + half4 * Math.sin(rad4);

        for (let i = 1; i < rect._dots.length; i++) {
            const dot = rect._dots[i];
            let preIndex = i - 1;
            rect._edges[preIndex].x1 = rect._dots[preIndex].x;
            rect._edges[preIndex].y1 = rect._dots[preIndex].y;
            rect._edges[preIndex].x2 = dot.x;
            rect._edges[preIndex].y2 = dot.y;
        }

        let index = rect.degeCount - 1;
        rect._edges[index].x1 = rect._dots[index].x;
        rect._edges[index].y1 = rect._dots[index].y;
        rect._edges[index].x2 = rect._dots[0].x;
        rect._edges[index].y2 = rect._dots[0].y;

        for (const edge of rect._edges) {
            if (edge.size == undefined) {
                let s1 = edge.x1 - edge.x2;
                let s2 = edge.y1 - edge.y2;
                edge.size = Math.sqrt(s1 * s1 + s2 * s2);
            }
        }
    },
    /**
     * 限制范围取值
     * @param {*} value 数字
     * @param {*} range 数字范围数组，存最大最小值 [minValue, maxValue]
     * @returns 
     */
    limitValue: function (value, range) {
        if (value < range[0]) {
            return range[0];
        }
        if (value > range[1]) {
            return range[1];
        }
        return value;
    },
    /**
     * 等于任意一个值就返回真
     * @param {*} value 数字
     * @param {*} values 数字数组
     */
    anyEqual: function (value, values) {
        for (const v of values) {
            if (v === value) {
                return true;
            }
        }
        return false;
    }
};

let ActionUtil = {
    lookAt: function (from, to) {
        let fromX = from._baseCenterX + from.rotateCenterX;
        let fromY = from._baseCenterY + from.rotateCenterY;
        from.rotation = MathUtil.calcLineRotation(fromX, fromY, to._centerX, to._centerY);
        from.direction = from.rotation;
    },
    lookAtArray: function (from, arrayTo, index) {
        index = Math.min(0, index || 0);
        if (arrayTo.length > index) {
            let to = arrayTo[index];
            this.lookAt(from, to);
            return true;
        }
        return false;
    },
    lookAtNearest: function (from, arrayTo) {
        arrayTo = arrayTo || [];
        if (arrayTo.length == 0) {
            return false;
        }
        let _to;
        let min = Number.MAX_VALUE;
        for (const to of arrayTo) {
            let d = MathUtil.calcLineLength(from._centerX, from._centerY, to._centerX, to._centerY);
            if (d < min) {
                _to = to;
                min = d;
            }
        }
        this.lookAt(from, _to);
        return true;
    }
}